/*
 * ml20gui - The MSN Webcam Recorder
 * This source code is under the GNU Public License (GPL).
 * Written by Ramiro Polla
 * Initial release: june 3rd, 2005
 *
 * this program records MSN Webcam Video
 */

#include "ml20gui.h"
#include "iplist.h"

int nochecksum = 0;

static stream_t *process_tcp(const uint8_t *data, uint32_t length, uint32_t src, uint32_t dst);
static stream_t *process_udp(const uint8_t *data, uint32_t length, uint32_t src, uint32_t dst);

/* taken from TCP/IP Illustrated Vol. 2(1995) by Gary R. Wright and W. Richard Stevens. Page 236 */
static uint16_t cksum(struct ip *ip, int len)
{
    uint32_t sum = 0;
    uint16_t *ip_16 = (uint16_t *) ip;

    while(len > 1) {
        sum += *ip_16++;
        if(sum & 0x80000000) /* if high order bit set, fold */
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }

    if(len) /* take care of left over byte */
        sum += (uint16_t) *(uint8_t *)ip;

    while(sum>>16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    return (uint16_t) ~sum;
}

stream_t *process_ip(const uint8_t *data, uint32_t caplen)
{
    const    struct ip *ip_header = (struct ip *) data;
    u_int    ip_header_len = (ip_header->ver_ihl & 0xf) * 4;
    u_int    ip_total_len = ntohs(ip_header->ip_len);
    uint8_t *new_data = (uint8_t*) data + ip_header_len;
    u_int    new_len = ip_total_len - ip_header_len;
    struct ip tip_header;
    uint16_t ipoff = ntohs(ip_header->ip_off);
    iplist_p iplist = 0;
    stream_t *ret = (stream_t *) 0;

    memcpy( &tip_header, ip_header, sizeof(struct ip) );
    tip_header.ip_sum = 0;

    //checksum
    if( !nochecksum )
        if( cksum( &tip_header, ip_header_len ) != ip_header->ip_sum )
            return 0;

    //fragmented
    // it is fragmented if 'more' flags set ( & 0x2000 )
    // or if offset ( & 0x1fff )
    if( ipoff & 0x3fff ) {
        uint16_t fragid = ntohs(ip_header->ip_id);
        uint16_t ipoffset = (ipoff & 0x1fff) * 8;
        uint16_t totallength = ipoffset + new_len;

        iplist = iplist_add( fragid );

        //clean all fragmented packets that have exceeded timeout
        iplist_clean( );

        //resize data if needed
        if( iplist->length < totallength ) {
            iplist->data = realloc( iplist->data, totallength );
            iplist->data_finished = realloc( iplist->data_finished, totallength );
            memset( (iplist->data_finished + iplist->length), 0xff, (totallength - iplist->length) );
            iplist->length = totallength;
        }

        //copy the new data and unset data_finished
        {
            memcpy( (iplist->data + ipoffset), new_data, new_len );
            memset( (iplist->data_finished + ipoffset), 0x00, new_len );
        }

        // not last packet?
        if( ipoff & 0x2000 )
            return 0;

        //check if we got full data
        {
            int i;
            for( i = 0 ; i < iplist->length ; i++ )
                if( iplist->data_finished[i] )
                    return 0;

            new_data = iplist->data;
            new_len = iplist->length;
        }
    }

    if (ip_header->ip_p == IPPROTO_TCP) {
        //process tcp
        ret = process_tcp( new_data, new_len,
            ntohl(ip_header->ip_src.s_addr),
            ntohl(ip_header->ip_dst.s_addr));
    } else if (ip_header->ip_p == IPPROTO_UDP) {
        //process udp
        ret = process_udp( new_data, new_len,
            ntohl(ip_header->ip_src.s_addr),
            ntohl(ip_header->ip_dst.s_addr));
    }

    if( iplist )
        iplist_rem( iplist_list( iplist->id ) );
    return ret;
}

static stream_t *process_tcp(const uint8_t *data, uint32_t length, uint32_t src, uint32_t dst)
{
    struct tcphdr *tcp_header = (struct tcphdr *) data;
    u_int tcp_header_len = (tcp_header->th_off >> 4) * 4;
    tcp_seq seq;
    tcp_seq offset;
    stream_t *live;

    //if it's truncated
    if (length < sizeof(struct tcphdr))
        return 0;

    /* return if this packet doesn't have any data (e.g., just an ACK) */
    if (length <= tcp_header_len)
        return 0;

    /* recalculate the beginning of data and its length, moving past the
     * TCP header */
    data += tcp_header_len;
    length -= tcp_header_len;

    /* fill in the flow_t structure with info that identifies this flow */
    seq = ntohl(tcp_header->th_seq);

    live = identify_flow( src, dst, ntohs(tcp_header->th_sport), ntohs(tcp_header->th_dport), data, length );

    if( IS_SET( live->flags, FLOW_NEW ) && IS_SET( live->flags, FLOW_VC ) ) {
        // Wrong, VCs are only UDP
        free( live->pvc.data );
        memset( &live->pvc, 0, sizeof(live->pvc) );
        UNSET_BIT( live->flags, FLOW_NEW );
        UNSET_BIT( live->flags, FLOW_VC );
    }

    if( IS_SET( live->flags, FLOW_NEW ) ) {
        seq += live->offset;
        live->isn = seq;
        data += live->offset;
        length -= live->offset;
        SET_BIT( live->flags, FLOW_ML20 );
        live->fname = build_streamname( live );
    }

    if( IS_SET( live->flags, FLOW_ML20 ) && !IS_SET( live->flags, FLOW_DEAD ) ) {
        offset = seq - live->isn;

        if(offset >= 0xffff0000)
            return 0;

        if( !live->fp )
            if( !open_file( live ) )
                return 0;

        fseek((live->fp), offset, SEEK_SET);
        fwrite(data, length, 1, live->fp);
        fflush(live->fp);

        live->size = offset + length;
    }

    return live;
}

static stream_t *process_udp(const uint8_t *data, uint32_t length, uint32_t src, uint32_t dst)
{
    struct udphdr *udp_header = (struct udphdr *) data;
    unsigned int udp_headers_len = (sizeof(struct udphdr) + sizeof(struct udpinthdr));
    unsigned int udp_header_len = sizeof( struct udphdr );
    stream_t *live;

    //if it's truncated
    if (length < sizeof(struct udphdr))
        return 0;

    /* return if this packet doesn't have any data */
    if (length <= udp_headers_len)
        return 0;

    /* recalculate the beginning of data and its length, moving past the
     * UDP header */
    data += udp_header_len;
    length -= udp_header_len;

    live = identify_flow( src, dst, ntohs(udp_header->uh_sport), ntohs(udp_header->uh_dport), data, length );

    if( IS_SET( live->flags, FLOW_NEW ) ) {
        if( IS_SET( live->flags, FLOW_ML20 ) )
            SET_BIT( live->flags, FLOW_UDP );
        else
            SET_BIT( live->flags, FLOW_VC );
        live->fname = build_streamname( live );
    }

    if( IS_SET( live->flags, FLOW_VC | FLOW_ML20 ) && !IS_SET( live->flags, FLOW_DEAD ) ) {
        if( !live->fp )
            if( !open_file( live ) )
                return 0;

        if( IS_SET( live->flags, FLOW_VC ) ) {
            if( IS_SET( live->flags, FLOW_NEW ) ) {
                if( live->pvc.data ) {
                    dump_vc_data( live->fp, (uint8_t*) live->pvc.data, live->pvc.length );
                    free( live->pvc.data );
                    live->size += live->pvc.length;
                }
                memset( &live->pvc, 0, sizeof(live->pvc) );
            }
            dump_vc_data( live->fp, (uint8_t*) data, length );
            live->size += length;
        } else /* IS_SET( live->flags, FLOW_UDP ) */ {
            fwrite( &length, sizeof(uint32_t), 1, live->fp );
            fwrite( data, length, 1, live->fp );
            //live->size += (length - 9);
            // No, the play procedure depends on live->size being the size of the file.
            live->size += length + 4;
        }
    }

    return live;
}
